﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PerCederberg.Grammatica.Runtime;

namespace SafeScript.MetaModel
{
    public class FunctionTypeDef : ITypeDef
    {
        public List<ITypeDef> ParameterTypes = new List<ITypeDef>();
        public ITypeDef ReturnType;
        public bool IsStatic = false;
        
        public FunctionTypeDef()
        {
            // this.BaseClass = ObjectType.Self;
        }

        public bool IsEqualTo(ITypeDef typedef)
        {
            FunctionTypeDef f = typedef as FunctionTypeDef;
            if ( f == null )
                return false;
            //bool b1 = this is BindingTypeDef;
            //bool b2 = typedef is BindingTypeDef;
            //if ( b1 != b2 )
            //    return false;
            if (!this.ReturnType.IsEqualTo(f.ReturnType))
                return false;
            if (this.ParameterTypes.Count != f.ParameterTypes.Count)
                return false;
            for (var i = 0; i < this.ParameterTypes.Count; ++i)
                if (!this.ParameterTypes[i].IsEqualTo(f.ParameterTypes[i]))
                    return false;
            return true;
        }

        public static bool IsEqualTo(FunctionTypeDef t1, FunctionTypeDef t2)
        {
            if (!t1.ReturnType.IsEqualTo(t2.ReturnType))
                return false;
            if (t1.ParameterTypes.Count != t2.ParameterTypes.Count)
                return false;
            for (var i = 0; i < t1.ParameterTypes.Count; ++i)
                if (!t1.ParameterTypes[i].IsEqualTo(t2.ParameterTypes[i]))
                    return false;
            return true;
        }

        public bool AreParametersEqualTo(FunctionTypeDef f)
        {
            if (f == null)
                return false;
            if (this.ParameterTypes.Count != f.ParameterTypes.Count)
                return false;
            for (var i = 0; i < this.ParameterTypes.Count; ++i)
                if (!this.ParameterTypes[i].IsEqualTo(f.ParameterTypes[i]))
                    return false;
            return true;
        }

        public bool CanBeAssignedTo(ITypeDef typedef)
        {
            if (typedef is ObjectType)
                return true;
            return this.IsEqualTo(typedef);
        }

        public int GetInheritanceDepth(ITypeDef typedef)
        {
            if (typedef is Object)
                return 1;
            return this.IsEqualTo(typedef) ? 0 : -1;
        }

        public virtual ITypeDef Instantiate(Dictionary<TemplateParameterType,ITypeDef> templateArguments)
        {
            FunctionTypeDef f = new FunctionTypeDef();
            this.InstantiateIntern(f, templateArguments);
            return f;
        }

        protected void InstantiateIntern(FunctionTypeDef f, Dictionary<TemplateParameterType,ITypeDef> templateArguments)
        {
            f.IsStatic = this.IsStatic;
            foreach (ITypeDef t in this.ParameterTypes)
            {
                f.ParameterTypes.Add(t.Instantiate(templateArguments));
            }
            f.ReturnType = this.ReturnType.Instantiate(templateArguments);
        }
    }

    public class FunctionSetTypeDef : ITypeDef
    {
        public List<FunctionTypeDef> FunctionTypes = new List<FunctionTypeDef>();

        #region ITypeDef Members

        public bool IsEqualTo(ITypeDef typedef)
        {
            return this == typedef as FunctionSetTypeDef;
        }

        public bool CanBeAssignedTo(ITypeDef typedef)
        {
            return this.IsEqualTo(typedef);
        }

        public int GetInheritanceDepth(ITypeDef typedef)
        {
            return this.IsEqualTo(typedef) ? 0 : -1;
        }

        public ITypeDef Instantiate(Dictionary<TemplateParameterType,ITypeDef> templateArguments)
        {
            FunctionSetTypeDef s = new FunctionSetTypeDef();
            foreach (FunctionTypeDef f in this.FunctionTypes)
                s.FunctionTypes.Add(f.Instantiate(templateArguments) as FunctionTypeDef);
            return s;
        }

        #endregion

        public FunctionTypeDef FindFunction(List<ITypeDef> parameterTypes, ErrorReport report, Node errorLocation)
        {
            int[] best = new int[parameterTypes.Count];
            for (int i = 0; i < best.Length; ++i)
                best[i] = int.MaxValue;

            FunctionTypeDef result = null;
            bool multipleBest = false;
            foreach (FunctionTypeDef func in this.FunctionTypes)
            {
                if (func.ParameterTypes.Count != parameterTypes.Count)
                    continue;
                // Let's assume this one is better
                int cmp = 1;
                bool better = false;
                for (int i = 0; i < best.Length; ++i)
                {
                    // If the parameter is null -> skip it since it won't help
                    if (parameterTypes[i] is NullType || (parameterTypes[i] is InstanceType && (parameterTypes[i] as InstanceType).Type is NullType))
                        continue;
                    int depth = parameterTypes[i].GetInheritanceDepth(func.ParameterTypes[i]);
                    // The parameter does not match? -> Try next function
                    if (depth == -1 )
                    {
                        better = false;
                        cmp = -1;
                        break;
                    }
                    // This parameter fits worse than the parameter of the currently best?
                    if ( depth > best[i])
                    {
                        cmp = 0;
                    }
                    // This parameter fits better than the parameter of the currently best
                    else if (depth < best[i])      
                    {
                        best[i] = depth;
                        better = true;
                    }
                }

                // The function performs better or equally to the currently best one
                if (cmp == 1)
                {
                    // It actually performs no better -> it performs equally
                    if (!better && result != null)
                    {
                        result = func;
                        // This can lead to ambiguity
                        multipleBest = true;
                    }
                    else // This function is better than all before
                    {
                        multipleBest = false;
                        result = func;
                    }
                }
                // This function performed worse than the current best, but in one argument it had a better fit -> ambiguity
                else if ( cmp == 0 )
                {
                    if (better)
                        multipleBest = true;
                }
                // If cmp == -1 then the function did not match at all.
            }

            if (result == null)
            {
                report.Add(new ErrorEntry(errorLocation) { Type = ErrorType.NoMatchingFunction });
                return null;
            }

            if (multipleBest)
            {
                report.Add( new ErrorEntry(errorLocation) { Type = ErrorType.FunctionCallIsAmbiguous } );
                return null;
            }

            return result;
        }
    }
}
